返回文章列表
vuetypescript

Vue 3+Vite+TypeScript+Tailwind CSS 完整開發指南

從零開始搭建一個完整的現代化 Vue 3 前端開發環境,包含所有最佳實踐工具和配置

2026年4月7日 5 次瀏覽 haodai
Vue 3+Vite+TypeScript+Tailwind CSS 完整開發指南

Vue 3 + Vite + TypeScript + Tailwind CSS 完整開發指南

本文檔介紹如何從零開始搭建一個完整的現代化 Vue 3 前端開發環境,包含所有最佳實踐工具和配置。


技術棧介紹

核心框架

  • Vue 3 - 漸進式 JavaScript 框架,使用 Composition API
    官方文檔

  • Vite - 下一代前端構建工具,極速的開發體驗
    官方文檔

  • TypeScript - 提供類型安全的 JavaScript 超集
    官方文檔

UI 框架

  • Tailwind CSS 4 - 實用優先的 CSS 框架,v4 採用 CSS-first 配置,無需 tailwind.config.js
    官方文檔

狀態管理與路由

HTTP 客戶端

自動化工具

  • unplugin-auto-import - 自動導入 API
    GitHub

  • unplugin-vue-components - 自動註冊組件
    GitHub

代碼質量工具

  • ESLint - JavaScript/TypeScript 代碼檢查工具
    官方文檔

  • Oxlint - 基於 Rust 的超高速 Linter,比 ESLint 快 50-100 倍,可與 ESLint 並存
    官方文檔

  • Oxfmt - 基於 Rust 的超高速格式化工具,比 Prettier 快 30 倍,支援 Import 排序(Beta)
    官方文檔

  • Prettier - 代碼格式化工具(穩定替代方案)
    官方文檔

  • Commitlint - Git 提交訊息規範檢查
    官方文檔

  • Husky - Git hooks 管理工具
    官方文檔

  • lint-staged - 暫存文件代碼檢查
    GitHub

推薦庫

以下是一些在 Vue 3 項目中常用的推薦庫,可根據項目需求選擇安裝:


環境準備

系統要求

根據 Vue 官方推薦:

  • Node.js >= 20.19.0 或 >= 22.12.0

  • npm >= 10.0.0 或 pnpm >= 9.0.0

檢查版本

plain
1
2
3
4
5
6
7
8
9
# 檢查 Node.js 版本
node --version

# 檢查 npm 版本
npm --version

# 或檢查 pnpm 版本(推薦)
pnpm --version

安裝 pnpm(推薦)

plain
1
2
3
4
5
6
# 使用 npm 全局安裝 pnpm
npm install -g pnpm

# 或使用 Homebrew(macOS)
brew install pnpm

項目初始化

1. 使用 create-vue 創建項目

Vue 官方提供了 create-vue 腳手架工具,這是創建 Vue 3 項目的推薦方式。

plain
1
2
3
4
5
6
# 使用 npm
npm create vue@latest

# 或使用 pnpm(推薦)
pnpm create vue@latest

執行後會出現互動式選項,建議依需求勾選:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 請輸入專案名稱: my-vue-app
 是否使用 TypeScript? … 是/否
 是否啟用 JSX 支援? … 是/否
 是否引入 Vue Router(單頁應用)? … 是/否
 是否引入 Pinia(狀態管理)? … 是/否
 是否引入 Vitest(單元測試)? … 是/否
 是否引入 ESLint(程式碼檢查)? … 是/否
 是否引入 Prettier(程式碼格式化)? … 是/否

# 額外測試特性(依版本可能出現)
 Oxlint(超快靜態檢查器,與 ESLint 可並存)
 rolldown-vite(試驗性功能)

# 其他
 跳過所有範例程式碼,建立一個空白的 Vue 專案? … Yes/No

備註:選項會隨 create-vue 版本略有不同,上述為目前常見項目。若未勾選某功能(如 Router/Pinia/Vitest),可於後續依照本文對應章節補裝。

2. 安裝依賴

plain
1
2
3
4
5
6
7
8
cd my-vue-app

# 安裝依賴
npm install

# 或使用 pnpm
pnpm install

3. 測試運行

plain
1
2
3
4
5
6
# 使用 npm
npm run dev

# 或使用 pnpm
pnpm dev

訪問 http://localhost:5173 確認項目正常運行。


安裝與配置

1. TypeScript 配置

create-vue 已經為我們配置好了 TypeScript。項目使用了分離式的配置檔案:

  • tsconfig.json - 專案參考配置(主配置)

  • tsconfig.app.json - 應用程式碼配置

  • tsconfig.node.json - Node 工具配置(Vite、ESLint 等)

  • tsconfig.vitest.json - 測試配置

查看 tsconfig.json

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.node.json"
    },
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.vitest.json"
    }
  ]
}

查看 tsconfig.app.json

plain
1
2
3
4
5
6
7
8
9
10
11
12
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

路徑別名 @ 已經默認配置好了,可以直接使用。

查看 vite.config.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
})

Vite 配置已經包含了路徑別名和 Vue DevTools 插件,無需額外配置。

配置 Base 路徑

如果應用部署在非根路徑下(例如 localhost/web/),需要在 vite.config.ts 中配置 base 選項:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  base: '/web/', // 配置應用基礎路徑
  plugins: [
    vue(),
    vueDevTools(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
})

注意事項:

  • base 必須以 / 開頭和結尾(例如 /web/

  • 如果使用環境變數,可以通過 import.meta.env.BASE_URL 訪問

  • Vue Router 的 createWebHistory 會自動使用 import.meta.env.BASE_URL,無需額外配置

  • 開發環境和生產環境可以使用不同的 base 路徑,通過環境變數控制


2. Tailwind CSS 配置

推薦使用 Tailwind CSS 4:v4 採用全新 CSS-first 架構,無需 tailwind.config.js,透過 Vite 插件整合,設定更簡單。需要現代瀏覽器(Safari 16.4+、Chrome 111+、Firefox 128+)。

安裝依賴

plain
1
2
3
4
5
6
# 使用 npm
npm install -D tailwindcss @tailwindcss/vite

# 或使用 pnpm
pnpm add -D tailwindcss @tailwindcss/vite

注意:v4 不再需要 postcssautoprefixer

配置 vite.config.ts

在 Vite 配置中加入 Tailwind CSS 插件:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    tailwindcss(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
})

創建 src/assets/styles/tailwind.css

v4 使用 @import 取代舊版的 @tailwind 指令,並透過 @theme 定義自訂主題變數:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@import "tailwindcss";

/* 自訂主題變數(取代 tailwind.config.js 的 theme.extend) */
@theme {
  --color-primary-50: #f0f9ff;
  --color-primary-100: #e0f2fe;
  --color-primary-200: #bae6fd;
  --color-primary-300: #7dd3fc;
  --color-primary-400: #38bdf8;
  --color-primary-500: #0ea5e9;
  --color-primary-600: #0284c7;
  --color-primary-700: #0369a1;
  --color-primary-800: #075985;
  --color-primary-900: #0c4a6e;
  --color-primary-950: #082f49;
}

/* 自定義全局樣式 */
@layer base {
  body {
    @apply bg-gray-50 text-gray-900;
  }
}

/* v4 使用 @utility 取代 @layer components */
@utility btn-primary {
  @apply bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-colors;
}

@utility btn-secondary {
  @apply bg-gray-200 text-gray-800 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors;
}

@utility card {
  @apply bg-white rounded-lg shadow-md p-6;
}

v3 → v4 重要變更:

  • @tailwind base/components/utilities@import "tailwindcss"

  • @layer utilities/components@utility

  • tailwind.config.jstheme.extend → CSS @theme 變數

  • shadow-smshadow-xsshadowshadow-sm(陰影命名調整)

  • !important 修飾符:!flexflex!

src/main.ts 中引入

plain
1
2
3
4
5
6
import { createApp } from 'vue'
import './assets/styles/tailwind.css'
import App from './App.vue'

createApp(App).mount('#app')

從 v3 遷移

如需從 v3 自動遷移:

plain
1
2
npx @tailwindcss/upgrade

3. Vue Router 配置

若在 create-vue 勾選了 Router,本節可直接使用既有檔案;
若未勾選,請先安裝並初始化:

plain
1
2
3
4
5
# 使用 npm
npm install vue-router
# 或 pnpm
pnpm add vue-router

查看 src/router/index.ts

系統已自動生成路由配置文件,我們可以在此基礎上進行擴展:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import type { App } from 'vue'

// 路由配置
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/HomeView.vue'),
    meta: {
      title: '首頁'
    }
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/AboutView.vue'),
    meta: {
      title: '關於'
    }
  }
]

// 創建路由實例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  }
})

// 全局前置守衛
router.beforeEach((to, from, next) => {
  // 設置頁面標題
  document.title = (to.meta.title as string) || 'Vue App'
  
  // 檢查是否需要登入
  if (to.meta.requiresAuth) {
    // 這裡可以檢查登入狀態
    const isAuthenticated = localStorage.getItem('token')
    if (!isAuthenticated) {
      next({ name: 'Home' })
      return
    }
  }
  
  next()
})

// 全局後置鉤子
router.afterEach((to, from) => {
  // 可以在這裡做一些統計或日誌記錄
  console.log(`路由從 ${from.path} 到 ${to.path}`)
})

// 導出安裝函數
export function setupRouter(app: App) {
  app.use(router)
}

export default router

更新 src/main.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
import { createApp } from 'vue'
import './assets/styles/tailwind.css'
import App from './App.vue'
import { setupRouter } from './router'

const app = createApp(App)

// 配置路由
setupRouter(app)

app.mount('#app')

創建視圖文件

創建 src/views/HomeView.vue

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-4xl font-bold text-center mb-8">歡迎使用 Vue 3</h1>
    <div class="card max-w-2xl mx-auto">
      <p class="text-lg mb-4">這是一個使用以下技術構建的現代化 Vue 3 應用:</p>
      <ul class="list-disc list-inside space-y-2">
        <li>Vue 3 + Composition API</li>
        <li>Vite 5 - 極速構建工具</li>
        <li>TypeScript - 類型安全</li>
        <li>Tailwind CSS 4 - 實用優先的 CSS</li>
        <li>Pinia - 狀態管理</li>
        <li>Vue Router - 路由管理</li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts">
// 使用 Composition API
</script>

更新 src/App.vue

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
  <div id="app">
    <nav class="bg-white shadow-lg">
      <div class="container mx-auto px-4">
        <div class="flex justify-between items-center h-16">
          <router-link to="/" class="text-xl font-bold text-primary-600">
            Vue App
          </router-link>
          <div class="flex gap-4">
            <router-link 
              to="/" 
              class="text-gray-700 hover:text-primary-600 transition-colors"
              active-class="text-primary-600 font-semibold"
            >
              首頁
            </router-link>
            <router-link 
              to="/about" 
              class="text-gray-700 hover:text-primary-600 transition-colors"
              active-class="text-primary-600 font-semibold"
            >
              關於
            </router-link>
          </div>
        </div>
      </div>
    </nav>
    
    <main class="min-h-screen">
      <router-view />
    </main>
    
    <footer class="bg-gray-800 text-white py-8">
      <div class="container mx-auto px-4 text-center">
        <p>&copy; 2025 Vue App. All rights reserved.</p>
      </div>
    </footer>
  </div>
</template>

<script setup lang="ts">
</script>

4. Pinia 狀態管理

若在 create-vue 勾選了 Pinia,專案已可直接使用;
若未勾選,請先安裝:

plain
1
2
3
4
5
# 使用 npm
npm install pinia
# 或 pnpm
pnpm add pinia

如果需要持久化功能,可以安裝插件:

plain
1
2
3
4
5
6
# 使用 npm
npm install pinia-plugin-persistedstate

# 或使用 pnpm
pnpm add pinia-plugin-persistedstate

修改 src/main.ts 添加持久化插件

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

import App from './App.vue'
import router from './router'

const app = createApp(App)

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

app.use(pinia)
app.use(router)

app.mount('#app')

創建 src/stores/user.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 定義用戶接口
interface User {
  id: number
  name: string
  email: string
  avatar?: string
}

// 使用 Composition API 風格定義 Store
export const useUserStore = defineStore('user', () => {
  // State
  const user = ref<User | null>(null)
  const token = ref<string>('')
  
  // Getters
  const isAuthenticated = computed(() => !!token.value)
  const userName = computed(() => user.value?.name || '訪客')
  
  // Actions
  function login(userData: User, authToken: string) {
    user.value = userData
    token.value = authToken
  }
  
  function logout() {
    user.value = null
    token.value = ''
  }
  
  return {
    user,
    token,
    isAuthenticated,
    userName,
    login,
    logout
  }
}, {
  // 配置持久化
  persist: {
    key: 'user-store',
    storage: localStorage,
    paths: ['user', 'token']
  }
})

5. Axios 配置

安裝依賴

plain
1
2
3
4
5
6
# 使用 npm
npm install axios

# 或使用 pnpm
pnpm add axios

創建 src/utils/request.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { useUserStore } from '@/stores/user'

// API 基礎配置
const baseURL = import.meta.env.VITE_API_BASE_URL || '/api'
const timeout = 15000

// 創建 axios 實例
const service: AxiosInstance = axios.create({
  baseURL,
  timeout,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

// 請求攔截器
service.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()
    
    // 添加 token
    if (userStore.token) {
      config.headers.Authorization = `Bearer ${userStore.token}`
    }
    
    return config
  },
  (error: AxiosError) => {
    console.error('請求錯誤:', error)
    return Promise.reject(error)
  }
)

// 響應攔截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    const res = response.data
    
    // 根據實際後端接口調整
    if (res.code !== 200 && res.code !== 0) {
      console.error('接口錯誤:', res.message)
      
      // 401: 未授權
      if (res.code === 401) {
        const userStore = useUserStore()
        userStore.logout()
        window.location.href = '/login'
      }
      
      return Promise.reject(new Error(res.message || '請求失敗'))
    }
    
    return res
  },
  (error: AxiosError) => {
    let message = '請求失敗'
    
    if (error.response) {
      switch (error.response.status) {
        case 400:
          message = '請求參數錯誤'
          break
        case 401:
          message = '未授權,請重新登入'
          const userStore = useUserStore()
          userStore.logout()
          window.location.href = '/login'
          break
        case 403:
          message = '拒絕訪問'
          break
        case 404:
          message = '請求資源不存在'
          break
        case 500:
          message = '服務器內部錯誤'
          break
        default:
          message = `連接錯誤 ${error.response.status}`
      }
    } else if (error.message) {
      if (error.message.includes('timeout')) {
        message = '請求超時'
      } else if (error.message.includes('Network Error')) {
        message = '網絡錯誤'
      }
    }
    
    console.error('響應錯誤:', message)
    
    return Promise.reject(error)
  }
)

// 導出 axios 實例
export default service

// 封裝常用方法
export const request = {
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return service.get(url, config)
  },
  
  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return service.post(url, data, config)
  },
  
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return service.put(url, data, config)
  },
  
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return service.delete(url, config)
  },
  
  patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return service.patch(url, data, config)
  }
}

創建 src/api/user.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { request } from '@/utils/request'

// 定義接口返回類型
export interface LoginParams {
  username: string
  password: string
}

export interface LoginResponse {
  token: string
  user: {
    id: number
    name: string
    email: string
  }
}

export interface UserInfo {
  id: number
  name: string
  email: string
}

// 用戶相關 API
export const userApi = {
  // 登入
  login(data: LoginParams) {
    return request.post<LoginResponse>('/auth/login', data)
  },
  
  // 獲取用戶信息
  getUserInfo() {
    return request.get<UserInfo>('/user/info')
  }
}

創建 src/api/index.ts

plain
1
2
3
4
5
6
7
// 統一導出所有 API
export * from './user'

// 如果有其他模塊的 API,可以繼續添加
// export * from './product'
// export * from './order'

API 使用示例

創建 src/composables/useAuth.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { userApi, type LoginParams } from '@/api'

export function useAuth() {
  const router = useRouter()
  const userStore = useUserStore()
  
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  // 登入
  async function login(params: LoginParams) {
    loading.value = true
    error.value = null
    
    try {
      const response = await userApi.login(params)
      userStore.login(response.user, response.token)
      router.push('/')
      return true
    } catch (err: any) {
      error.value = err.message || '登入失敗'
      return false
    } finally {
      loading.value = false
    }
  }
  
  // 獲取用戶信息
  async function fetchUserInfo() {
    loading.value = true
    error.value = null
    
    try {
      const userInfo = await userApi.getUserInfo()
      userStore.login(userInfo, userStore.token)
      return userInfo
    } catch (err: any) {
      error.value = err.message || '獲取用戶信息失敗'
      return null
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    error,
    login,
    fetchUserInfo
  }
}

環境變數配置

創建 .env 文件:

plain
1
2
3
4
5
6
7
8
9
10
# 應用配置
VITE_APP_TITLE=Vue App
VITE_APP_VERSION=1.0.0

# API 配置
VITE_API_BASE_URL=http://localhost:3000/api

# 其他配置
VITE_USE_MOCK=false

創建 .env.development 文件:

plain
1
2
3
4
# 開發環境
VITE_API_BASE_URL=http://localhost:3000/api
VITE_USE_MOCK=true

創建 .env.production 文件:

plain
1
2
3
4
# 生產環境
VITE_API_BASE_URL=https://api.example.com
VITE_USE_MOCK=false

6. 自動載入組件配置(可選)

這是一個可選的優化配置,可以自動導入 Vue API 和自動註冊組件。

安裝依賴

plain
1
2
3
4
5
6
# 使用 npm
npm install -D unplugin-auto-import unplugin-vue-components

# 或使用 pnpm
pnpm add -D unplugin-auto-import unplugin-vue-components

更新 vite.config.ts

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

// https://vite.dev/config/
export default defineConfig({
  // base: '/web/', // 如果部署在非根路徑,取消註釋並設置對應路徑
  plugins: [
    vue(),
    vueDevTools(),
    tailwindcss(),
    
    // 自動導入 API
    AutoImport({
      // 導入目標
      imports: [
        'vue',
        'vue-router',
        'pinia',
        {
          'axios': [
            ['default', 'axios']
          ]
        }
      ],
      // 自動導入的目錄
      dirs: [
        'src/composables/**',
        'src/stores/**',
        'src/utils/**'
      ],
      // 生成的類型聲明文件位置
      dts: 'src/auto-imports.d.ts',
      // ESLint 支持
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
        globalsPropValue: true
      }
    }),
    
    // 自動註冊組件
    Components({
      // 要搜索組件的目錄
      dirs: ['src/components'],
      // 組件的有效文件擴展名
      extensions: ['vue'],
      // 搜索子目錄
      deep: true,
      // 生成的類型聲明文件位置
      dts: 'src/components.d.ts'
    })
  ],
  
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
  
  server: {
    port: 5173,
    // 代理配置
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

更新 .gitignore

將自動生成的文件加入 .gitignore

plain
1
2
3
4
5
# 自動生成的類型聲明文件
src/auto-imports.d.ts
src/components.d.ts
.eslintrc-auto-import.json

創建組件示例

創建 src/components/HelloWorld.vue

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
  <div class="card">
    <h2 class="text-2xl font-bold mb-4">{{ title }}</h2>
    <p class="text-gray-600 mb-4">{{ message }}</p>
    <button @click="handleClick" class="btn-primary">
      點擊次數: {{ clickCount }}
    </button>
  </div>
</template>

<script setup lang="ts">
// 不需要導入 ref,已經自動導入
interface Props {
  title?: string
  message?: string
}

withDefaults(defineProps<Props>(), {
  title: 'Hello World',
  message: '這是一個自動註冊的組件'
})

const clickCount = ref(0)

function handleClick() {
  clickCount.value++
}
</script>

在任何 Vue 文件中使用,無需導入:

plain
1
2
3
4
5
6
7
8
<template>
  <HelloWorld title="歡迎" message="組件已自動註冊" />
</template>

<script setup lang="ts">
// 無需導入 ref, computed 等 API,已自動導入
</script>

7. ESLint + Oxlint 配置

create-vue 已經為我們配置好了 ESLint 9(使用新的 flat config 格式)。現在推薦搭配 Oxlint 一起使用,作為快速的第一道 lint 防線。

若在建立專案時沒有勾選 ESLint/Prettier,可後補安裝:

plain
1
2
3
4
5
6
# 使用 npm
npm install -D eslint eslint-plugin-vue @vue/eslint-config-typescript @vue/eslint-config-prettier

# 或 pnpm
pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript @vue/eslint-config-prettier

安裝 Oxlint

plain
1
2
3
4
5
6
# 使用 npm
npm install -D oxlint eslint-plugin-oxlint

# 或使用 pnpm
pnpm add -D oxlint eslint-plugin-oxlint

eslint-plugin-oxlint 會自動關閉 ESLint 中與 Oxlint 重疊的規則,避免重複。

配置 oxlint.json

plain
1
2
3
4
5
6
7
8
9
{
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/utils/schema.json",
  "rules": {
    "no-unused-vars": "warn",
    "no-console": "warn"
  },
  "ignorePatterns": ["dist", "node_modules", "*.d.ts"]
}

更新 eslint.config.ts(整合 Oxlint)

查看 eslint.config.ts

系統已自動生成 ESLint 配置文件,使用了新的 flat config 格式。加入 oxlint 整合後:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import oxlint from 'eslint-plugin-oxlint'

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  {
    name: 'app/files-to-ignore',
    ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
  },

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  
  {
    ...pluginVitest.configs.recommended,
    files: ['src/**/__tests__/*'],
  },
  
  // 關閉與 Oxlint 重疊的 ESLint 規則
  oxlint.configs['flat/recommended'],
  
  skipFormatting,
)

如果使用了自動導入插件

需要添加自動導入的 ESLint 配置:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import autoImportGlobals from './.eslintrc-auto-import.json' with { type: 'json' }

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  {
    name: 'app/files-to-ignore',
    ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
  },

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  
  {
    ...pluginVitest.configs.recommended,
    files: ['src/**/__tests__/*'],
  },
  
  // 添加自動導入的全局變數
  {
    languageOptions: {
      globals: autoImportGlobals.globals
    }
  },
  
  skipFormatting,
)

自定義規則

如果需要自定義規則,可以在配置文件末尾添加:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
export default defineConfigWithVueTs(
  // ... 其他配置
  
  // 自定義規則
  {
    rules: {
      'vue/multi-word-component-names': 'off',
      'vue/no-v-html': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
    }
  }
)

package.json 中的腳本

系統已經配置好了 lint 腳本:

plain
1
2
3
4
5
6
{
  "scripts": {
    "lint": "eslint . --fix --cache"
  }
}

8. 格式化工具配置

方案一:Oxfmt(推薦嘗試,Beta 版)

Oxfmt 是基於 Rust 的格式化工具,比 Prettier 快約 30 倍,內建 Import 排序、Tailwind CSS class 排序等功能。目前為 Beta 版本,已通過 100% Prettier 的 JS/TS 一致性測試。

注意:Oxfmt 目前對 Vue SFC(.vue)的支援仍在開發中,若需格式化 .vue 文件建議搭配 Prettier。

安裝

plain
1
2
3
4
5
6
# 使用 npm
npm install -D oxfmt

# 或使用 pnpm
pnpm add -D oxfmt

package.json 腳本

plain
1
2
3
4
5
6
7
{
  "scripts": {
    "fmt": "oxfmt",
    "fmt:check": "oxfmt --check"
  }
}

方案二:Prettier(穩定方案)

create-vue 已經為我們配置好了 Prettier,無需額外安裝。

查看 .prettierrc.json

系統已自動生成 Prettier 配置文件:

plain
1
2
3
4
5
6
7
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "singleQuote": true,
  "printWidth": 100
}

自定義配置(可選)

如果需要調整配置,可以修改 .prettierrc.json

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "vueIndentScriptAndStyle": false
}

創建 .prettierignore

plain
1
2
3
4
5
6
7
8
node_modules
dist
dist-ssr
coverage
*.d.ts
pnpm-lock.yaml
package-lock.json

package.json 中的腳本

系統已經配置好了 format 腳本:

plain
1
2
3
4
5
6
{
  "scripts": {
    "format": "prettier --write src/"
  }
}

9. Git Hooks 配置(可選)

這是一個可選的配置,用於在 Git 提交時自動檢查和格式化代碼。

安裝依賴

plain
1
2
3
4
5
6
# 使用 npm
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional

# 或使用 pnpm
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional

初始化 Husky

plain
1
2
3
4
5
6
# 使用 npm
npx husky init

# 或使用 pnpm
pnpm exec husky init

創建 commitlint.config.js

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',     // 新功能
        'fix',      // 修復 bug
        'docs',     // 文檔更新
        'style',    // 代碼格式(不影響代碼運行的變動)
        'refactor', // 重構(既不是新增功能,也不是修復 bug 的代碼變動)
        'perf',     // 性能優化
        'test',     // 增加測試
        'chore',    // 構建過程或輔助工具的變動
        'revert',   // 回滾
        'build'     // 構建系統或外部依賴項的更改
      ]
    ],
    'type-case': [2, 'always', 'lower-case'],
    'type-empty': [2, 'never'],
    'scope-case': [2, 'always', 'lower-case'],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
    'header-max-length': [2, 'always', 100]
  }
}

配置 Git Hooks

編輯 .husky/pre-commit

plain
1
2
3
4
5
6
7
8
9
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# 使用 npm
npm run lint-staged

# 或使用 pnpm
# pnpm lint-staged

創建 .husky/commit-msg

plain
1
2
3
4
5
6
7
8
9
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# 使用 npm
npx --no-install commitlint --edit $1

# 或使用 pnpm
# pnpm exec commitlint --edit $1

配置 lint-staged

package.json 中添加:

plain
1
2
3
4
5
6
7
8
9
10
11
12
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx,vue}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss,less,html,json,md}": [
      "prettier --write"
    ]
  }
}

完整的 package.json 示例

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
  "name": "my-vue-app",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "engines": {
    "node": "^20.19.0 || >=22.12.0"
  },
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check \"build-only {@}\" --",
    "preview": "vite preview",
    "test:unit": "vitest",
    "build-only": "vite build",
    "type-check": "vue-tsc --build",
    "lint": "oxlint && eslint . --fix --cache",
    "format": "prettier --write src/",
    "prepare": "husky"
  },
  "dependencies": {
    "vue": "^3.5.22",
    "vue-router": "^4.6.3",
    "pinia": "^3.0.3",
    "pinia-plugin-persistedstate": "^4.0.0",
    "axios": "^1.9.0"
  },
  "devDependencies": {
    "@tsconfig/node22": "^22.0.2",
    "@types/jsdom": "^27.0.0",
    "@types/node": "^22.18.11",
    "@vitejs/plugin-vue": "^6.0.1",
    "@vitest/eslint-plugin": "^1.3.23",
    "@vue/eslint-config-prettier": "^10.2.0",
    "@vue/eslint-config-typescript": "^14.6.0",
    "@vue/test-utils": "^2.4.6",
    "@vue/tsconfig": "^0.8.1",
    "eslint": "^9.37.0",
    "eslint-plugin-vue": "~10.5.0",
    "jsdom": "^27.0.1",
    "npm-run-all2": "^8.0.4",
    "prettier": "3.6.2",
    "typescript": "~5.9.0",
    "vite": "^7.1.11",
    "vite-plugin-vue-devtools": "^8.0.3",
    "vitest": "^3.2.4",
    "vue-tsc": "^3.1.1",
    
    "tailwindcss": "^4.1.0",
    "@tailwindcss/vite": "^4.1.0",
    
    "oxlint": "^1.0.0",
    "eslint-plugin-oxlint": "^1.0.0",
    
    "unplugin-auto-import": "^19.0.0",
    "unplugin-vue-components": "^28.0.0",
    
    "husky": "^9.0.0",
    "lint-staged": "^15.2.0",
    "@commitlint/cli": "^19.0.0",
    "@commitlint/config-conventional": "^19.0.0"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx,vue}": [
      "oxlint --fix",
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss,json,md}": [
      "prettier --write"
    ]
  }
}

Git 提交訊息規範

提交訊息格式:

plain
1
2
3
4
5
6
<type>(<scope>): <subject>

<body>

<footer>

示例:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 新功能
git commit -m "feat(user): 新增用戶登入功能"

# 修復 bug
git commit -m "fix(router): 修復路由導航錯誤"

# 文檔更新
git commit -m "docs(readme): 更新安裝說明"

# 代碼格式調整
git commit -m "style(app): 格式化代碼"

# 重構
git commit -m "refactor(store): 重構用戶狀態管理"

# 性能優化
git commit -m "perf(list): 優化列表渲染性能"

# 測試
git commit -m "test(user): 新增用戶模塊測試"

# 構建相關
git commit -m "chore(deps): 更新依賴版本"

Type 說明:

  • feat: 新功能

  • fix: 修復 bug

  • docs: 文檔更新

  • style: 代碼格式調整(不影響功能)

  • refactor: 代碼重構

  • perf: 性能優化

  • test: 測試相關

  • chore: 構建工具或輔助工具的變動

  • revert: 回滾提交

  • build: 構建系統或外部依賴的更改


項目結構

使用 create-vue 生成的項目結構,並添加額外配置後:

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
my-vue-app/
├── .husky/                     # Git hooks(可選)
   ├── _/
   ├── commit-msg
   └── pre-commit
├── .vscode/                    # VS Code 配置
   └── extensions.json
├── public/                     # 靜態資源
   └── favicon.ico
├── src/
   ├── api/                    # API 接口(自行創建)
   │   ├── index.ts
   │   └── user.ts
   ├── assets/                 # 資源文件
   │   ├── base.css
   │   ├── main.css
   │   └── logo.svg
   ├── components/             # 全局組件
   │   ├── __tests__/          # 組件測試
   │   ├── HelloWorld.vue
   │   ├── TheWelcome.vue
   │   └── WelcomeItem.vue
   ├── composables/            # 組合式函數(自行創建)
   │   └── useAuth.ts
   ├── router/                 # 路由配置
   │   └── index.ts
   ├── stores/                 # Pinia stores
   │   ├── counter.ts
   │   └── user.ts            # 自行創建
   ├── utils/                  # 工具函數(自行創建)
   │   └── request.ts
   ├── views/                  # 頁面組件
   │   ├── HomeView.vue
   │   └── AboutView.vue
   ├── App.vue                 # 根組件
   ├── main.ts                 # 入口文件
   ├── auto-imports.d.ts       # 自動生成(如果使用自動導入)
   └── components.d.ts         # 自動生成(如果使用自動導入)
├── .env                        # 環境變數(自行創建)
├── .env.development            # 開發環境變數(自行創建)
├── .env.production             # 生產環境變數(自行創建)
├── .editorconfig               # 編輯器配置
├── .eslintrc-auto-import.json  # 自動生成(如果使用自動導入)
├── .gitattributes              # Git 屬性
├── .gitignore                  # Git 忽略
├── .prettierrc.json            # Prettier 配置
├── commitlint.config.js        # Commitlint 配置(可選)
├── env.d.ts                    # 環境變數類型定義
├── eslint.config.ts            # ESLint 配置(flat config)
├── index.html                  # HTML 入口
├── oxlint.json                 # Oxlint 配置(可選)
├── package.json                # 項目配置
├── README.md                   # 項目說明
├── tsconfig.app.json           # 應用 TypeScript 配置
├── tsconfig.json               # TypeScript 主配置
├── tsconfig.node.json          # Node TypeScript 配置
├── tsconfig.vitest.json        # Vitest TypeScript 配置
├── vite.config.ts              # Vite 配置
└── vitest.config.ts            # Vitest 配置

使用指南

開發流程

1. 啟動開發服務器

plain
1
2
3
4
5
6
# 使用 npm
npm run dev

# 或使用 pnpm
pnpm dev

訪問 http://localhost:5173

2. 創建新頁面

plain
1
2
3
# 創建新的視圖組件
touch src/views/AboutView.vue
plain
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold mb-6">關於頁面</h1>
    <div class="card">
      <p class="text-gray-600">這是關於頁面的內容。</p>
    </div>
  </div>
</template>

<script setup lang="ts">
// 使用自動導入的 API,無需手動 import
</script>

在路由中註冊:

plain
1
2
3
4
5
6
7
8
9
10
// src/router/index.ts
{
  path: '/about',
  name: 'About',
  component: () => import('@/views/AboutView.vue'),
  meta: {
    title: '關於'
  }
}

3. 創建 Store

plain
1
2
touch src/stores/user.ts
plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const token = ref<string>('')
  
  const isAuthenticated = computed(() => !!token.value)
  
  function login(userData: User, authToken: string) {
    user.value = userData
    token.value = authToken
  }
  
  function logout() {
    user.value = null
    token.value = ''
  }
  
  return {
    user,
    token,
    isAuthenticated,
    login,
    logout
  }
}, {
  persist: {
    key: 'user-store',
    storage: localStorage
  }
})

4. 創建 API

plain
1
2
touch src/api/user.ts
plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { request } from '@/utils/request'

export interface LoginParams {
  username: string
  password: string
}

export interface LoginResponse {
  token: string
  user: {
    id: number
    name: string
    email: string
  }
}

export const userApi = {
  login(data: LoginParams) {
    return request.post<LoginResponse>('/auth/login', data)
  },
  
  getUserInfo() {
    return request.get('/user/info')
  }
}

代碼檢查與格式化

檢查代碼

plain
1
2
3
4
5
6
7
8
9
10
11
12
# 運行 Oxlint(快速第一道檢查)
npx oxlint .
# 或 pnpm exec oxlint .

# 運行 ESLint 檢查(含修復)
npm run lint
# 或 pnpm lint

# 運行 Prettier 格式化
npm run format
# 或 pnpm format

提交代碼

plain
1
2
3
4
5
6
7
8
9
# 添加文件到暫存區
git add .

# 提交(會自動運行 lint-staged 和 commitlint)
git commit -m "feat(product): 新增產品列表頁面"

# 推送
git push

構建與部署

構建生產版本

plain
1
2
3
4
5
6
# 構建
npm run build
# 或 pnpm build

# 構建產物在 dist/ 目錄

預覽生產構建

plain
1
2
3
npm run preview
# 或 pnpm preview

部署到 Nginx

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
    listen 80;
    server_name example.com;
    root /var/www/my-vue-app/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

最佳實踐

1. 組件設計

單一職責原則

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- ❌ 不好:組件做太多事情 -->
<template>
  <div>
    <header>...</header>
    <nav>...</nav>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

<!-- ✅ 好:拆分成多個組件 -->
<template>
  <div>
    <AppHeader />
    <AppNav />
    <main>
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

Props 驗證

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ 不好:沒有類型定義
const props = defineProps({
  title: String,
  count: Number
})

// ✅ 好:使用 TypeScript 接口
interface Props {
  title: string
  count?: number
  items: Item[]
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})

2. Composables 設計

可復用邏輯

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// ✅ 好:創建可復用的 composable
export function useLocalStorage<T>(key: string, initialValue: T) {
  const storedValue = ref<T>(initialValue)
  
  // 從 localStorage 讀取
  function read() {
    try {
      const item = window.localStorage.getItem(key)
      storedValue.value = item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(error)
      storedValue.value = initialValue
    }
  }
  
  // 寫入 localStorage
  function write(value: T) {
    try {
      storedValue.value = value
      window.localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.error(error)
    }
  }
  
  // 刪除
  function remove() {
    try {
      window.localStorage.removeItem(key)
      storedValue.value = initialValue
    } catch (error) {
      console.error(error)
    }
  }
  
  // 初始化時讀取
  read()
  
  return {
    value: storedValue,
    write,
    remove
  }
}

3. 狀態管理

Store 組織

plain
1
2
3
4
5
6
7
8
// ✅ 好:按功能模塊組織 store
stores/
├── index.ts      # 導出所有 store
├── user.ts       # 用戶相關
├── cart.ts       # 購物車
├── product.ts    # 產品
└── app.ts        # 應用配置

避免過度使用 Store

plain
1
2
3
4
5
6
7
// ❌ 不好:把所有狀態都放到 store
const appStore = useAppStore()
const localCount = computed(() => appStore.tempCount)

// ✅ 好:局部狀態使用 ref/reactive
const localCount = ref(0)

4. API 請求

統一錯誤處理

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ✅ 好:在 composable 中統一處理錯誤
export function useApi() {
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  async function request<T>(
    apiCall: () => Promise<T>,
    errorMessage = '請求失敗'
  ): Promise<T | null> {
    loading.value = true
    error.value = null
    
    try {
      const result = await apiCall()
      return result
    } catch (err: any) {
      error.value = err.message || errorMessage
      return null
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    error,
    request
  }
}

5. 性能優化

使用 v-memo

plain
1
2
3
4
5
6
7
<template>
  <!-- 當 item 沒變化時,跳過更新 -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">
    {{ item.name }}
  </div>
</template>

懶加載路由

plain
1
2
3
4
5
6
// ✅ 好:路由懶加載
{
  path: '/products',
  component: () => import('@/views/ProductView.vue')
}

使用 shallowRef

plain
1
2
3
4
5
// 對於大型數據結構,使用 shallowRef
const largeData = shallowRef({
  // 大量數據...
})

6. TypeScript 使用

定義清晰的類型

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ 好:定義清晰的接口
interface User {
  id: number
  name: string
  email: string
  avatar?: string
  role: 'admin' | 'user' | 'guest'
  createdAt: string
  updatedAt: string
}

// 使用工具類型
type UserUpdateData = Partial<Pick<User, 'name' | 'email' | 'avatar'>>

7. Tailwind CSS 使用

提取重複的樣式

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* ✅ 好:在 tailwind.css 中定義常用工具類(v4 使用 @utility) */
@utility btn {
  @apply px-4 py-2 rounded-lg font-medium transition-colors;
}

@utility btn-primary {
  @apply bg-primary-600 text-white hover:bg-primary-700;
  }
  
@utility card {
  @apply bg-white rounded-lg shadow-md p-6;
}

@utility input {
  @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500;
}

8. Git 提交規範

plain
1
2
3
4
5
6
7
8
9
10
# ✅ 好:清晰的提交訊息
git commit -m "feat(auth): 新增用戶登入功能"
git commit -m "fix(router): 修復路由導航守衛邏輯"
git commit -m "docs(readme): 更新安裝說明"

# ❌ 不好:模糊的提交訊息
git commit -m "update"
git commit -m "fix bug"
git commit -m "修改"

快速命令參考

plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 安裝依賴
npm install
# 或 pnpm install

# 啟動開發服務器
npm run dev
# 或 pnpm dev

# 構建生產版本
npm run build
# 或 pnpm build

# 預覽生產構建
npm run preview
# 或 pnpm preview

# 代碼檢查(Oxlint 快速檢查)
npx oxlint .

# 代碼檢查(ESLint 完整檢查 + 修復)
npm run lint
# 或 pnpm lint

# 代碼格式化
npm run format
# 或 pnpm format

# TypeScript 類型檢查
npm run type-check
# 或 pnpm type-check

# 運行單元測試
npm run test:unit
# 或 pnpm test:unit

# 清除緩存 (npm)
rm -rf node_modules .vite package-lock.json
npm install

# 清除緩存 (pnpm)
rm -rf node_modules .vite pnpm-lock.yaml
pnpm install